﻿using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Red.CommUtils
{
    internal class ProtoWriterBinary : comm.IProtoWriter
    {
        BinaryWriter buffer;
        Stack<long> bufferPositionStack;
        Stack<long> scopedBinaryPositionStack;

        public ProtoWriterBinary(Stream stream)
        {
            buffer = new BinaryWriter(stream);
            bufferPositionStack = new Stack<long>();
            scopedBinaryPositionStack = new Stack<long>();
        }

        public void PushScopedSizeCounter()
        {
            long currentPosition = buffer.BaseStream.Position;
            scopedBinaryPositionStack.Push(currentPosition);

            uint size = 0;
            buffer.Write(size);
        }

        public void PopScopedSizeCounter()
        {
            uint currentPosition = (uint)buffer.BaseStream.Position;
            uint writePosition = (uint)scopedBinaryPositionStack.Pop();
            uint positionDifference = (currentPosition - writePosition - sizeof(uint) ); // minus param size bytes

            buffer.Seek((int)writePosition, SeekOrigin.Begin);
            buffer.Write(positionDifference);
            buffer.Seek((int)currentPosition, SeekOrigin.Begin);
        }

        public bool BeginObject(string name, uint nameHash)
        {
            buffer.Write(nameHash);
            PushScopedSizeCounter();
            return true;
        }

        public void EndObject()
        {
            PopScopedSizeCounter();
        }

        public void BeginParams(uint maxParams)
        {
            long currentPosition = buffer.BaseStream.Position;
            bufferPositionStack.Push(currentPosition);
            buffer.Write(maxParams);
        }

        public void EndParams(uint numSavedParams)
        {
            long currentPosition = buffer.BaseStream.Position;
            long writePosition = bufferPositionStack.Pop();
            buffer.Seek((int)writePosition, SeekOrigin.Begin);
            buffer.Write(numSavedParams);
            buffer.Seek((int)currentPosition, SeekOrigin.Begin);
        }

        public void BeginParam(string name, uint nameHash)
        {
            buffer.Write(nameHash);
            PushScopedSizeCounter();
        }

        public void EndParam()
        {
            PopScopedSizeCounter();
        }

        public void WriteNull()
        {
            uint value = 0;
            buffer.Write(value);
        }

        public void WriteFloat(float value)
        {
            buffer.Write(value);
        }

        public void WriteDouble(double value)
        {
            buffer.Write(value);
        }

        public void WriteUint8(byte value)
        {
            buffer.Write(value);
        }

        public void WriteUint16(ushort value)
        {
            buffer.Write(value);
        }

        public void WriteUint32(uint value)
        {
            buffer.Write(value);
        }

        public void WriteUint64(ulong value)
        {
            buffer.Write(value);
        }

        public void WriteInt8(sbyte value)
        {
            buffer.Write(value);
        }

        public void WriteInt16(short value)
        {
            buffer.Write(value);
        }

        public void WriteInt32(int value)
        {
            buffer.Write(value);
        }

        public void WriteInt64(long value)
        {
            buffer.Write(value);
        }

        public void WriteBool(bool value)
        {
            buffer.Write(value);
        }

        public void WriteString(string value)
        {
            if ( string.IsNullOrEmpty( value ) )
            {
                WriteUint16( 0 );
                return;
            }

            WriteUint16( (ushort)value.Length );

            var data = value.ToArray();
            buffer.Write( data, 0, value.Length );
        }

        public void BeginArray(string name, uint typeHash, int numElements)
        {
            buffer.Write(typeHash);
            PushScopedSizeCounter();
            buffer.Write(numElements);
        }

        public void EndArray()
        {
            PopScopedSizeCounter();
        }
    }
}
